/* Grok X modifier mappings for shortcuts.

   Most of this code was taken from src/event-Xt.c in XEmacs 20.3-b17.
   The copyright(s) from the original XEmacs code are included below.

   Perpetrator: Sudish Joseph <sj@eng.mindspring.net>, Sept. 1997. */

/* The event_stream interface for X11 with Xt, and/or tty frames.
   Copyright (C) 1991, 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
   Copyright (C) 1995 Sun Microsystems, Inc.
   Copyright (C) 1996 Ben Wing.

   This file is part of XEmacs.

   XEmacs is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by the
   Free Software Foundation; either version 2, or (at your option) any
   later version.

   XEmacs is distributed in the hope that it will be useful, but WITHOUT
   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License along
   with XEmacs; see the file COPYING. if not, write to the
   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301 USA. */

#include "WM.h"

#include <string.h>
#include <strings.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>

#include <core/log_utils.h>

#include "WM.h"
#include "xmodifier.h"
#include "defaults.h"

/************************************************************************/
/*                            keymap handling                           */
/************************************************************************/

/* X bogusly doesn't define the interpretations of any bits besides
   ModControl, ModShift, and ModLock; so the Interclient Communication
   Conventions Manual says that we have to bend over backwards to figure
   out what the other modifier bits mean.  According to ICCCM:

   - Any keycode which is assigned ModControl is a "control" key.

   - Any modifier bit which is assigned to a keycode which generates Meta_L
   or Meta_R is the modifier bit meaning "meta".  Likewise for Super, Hyper,
   etc.

   - Any keypress event which contains ModControl in its state should be
   interpreted as a "control" character.

   - Any keypress event which contains a modifier bit in its state which is
   generated by a keycode whose corresponding keysym is Meta_L or Meta_R
   should be interpreted as a "meta" character.  Likewise for Super, Hyper,
   etc.

   - It is illegal for a keysym to be associated with more than one modifier
   bit.

   This means that the only thing that emacs can reasonably interpret as a
   "meta" key is a key whose keysym is Meta_L or Meta_R, and which generates
   one of the modifier bits Mod1-Mod5.

   This works with the default configurations of the 19 keyboard-types I've
   checked.
*/

static int HyperMask, SuperMask, AltMask;

static const char *index_to_name(int indice)
{
  switch (indice) {
    case ShiftMapIndex:
      return "ModShift";
    case LockMapIndex:
      return "ModLock";
    case ControlMapIndex:
      return "ModControl";
    case Mod1MapIndex:
      return "Mod1";
    case Mod2MapIndex:
      return "Mod2";
    case Mod3MapIndex:
      return "Mod3";
    case Mod4MapIndex:
      return "Mod4";
    case Mod5MapIndex:
      return "Mod5";
    default:
      return "???";
  }
}

int wXModifierFromKey(const char *key)
{
  if (strcasecmp(key, "Shift") == 0 && ShiftMask != 0) {
    return ShiftMask;
  } else if (strcasecmp(key, "Control") == 0 && ControlMask != 0) {
    return ControlMask;
  } else if ((strcasecmp(key, "Alt") == 0) && AltMask != 0) {
    return AltMask;
  } else if ((strcasecmp(key, "Super") == 0 || strcasecmp(key, "Alternate") == 0) &&
             SuperMask != 0) {
    return SuperMask;
  } else if (strcasecmp(key, "Hyper") == 0 && HyperMask != 0) {
    return HyperMask;
  } else if (strcasecmp(key, "Command") == 0) {
    return wPreferences.cmd_modifier_mask;
  } else if (strcasecmp(key, "Alternate") == 0) {
    return wPreferences.alt_modifier_mask;
  } else {
    return -1;
  }
}

void wXModifierInitialize(void)
{
  int modifier_index, modifier_key, column, mkpm;
  int hyper_bit = 0;
  int super_bit = 0;
  int alt_bit = 0;
  int mode_bit = 0;
  XModifierKeymap *x_modifier_keymap = XGetModifierMapping(dpy);

  mkpm = x_modifier_keymap->max_keypermod;
  for (modifier_index = 0; modifier_index < 8; modifier_index++) {
    for (modifier_key = 0; modifier_key < mkpm; modifier_key++) {
      KeySym last_sym = 0;

      for (column = 0; column < 4; column += 2) {
        KeyCode code;
        KeySym sym;

#define modwarn(key_name, old_mod, other_key)                                               \
  {                                                                                         \
    WMLogWarning(_("key %s (0x%x) generates %s, which is generated by %s"), key_name, code, \
                 index_to_name(old_mod), other_key);                                        \
  }

#define modbarf(key_name, other_mod)                                                    \
  {                                                                                     \
    WMLogWarning(_("key %s (0x%x) generates %s, which is nonsensical"), key_name, code, \
                 other_mod);                                                            \
  }

#define check_modifier(key_name, mask)                  \
  {                                                     \
    if ((1 << modifier_index) != mask)                  \
      modbarf(key_name, index_to_name(modifier_index)); \
  }

#define store_modifier(key_name, old_mod)                                                       \
  {                                                                                             \
    if (*old_mod && *old_mod != modifier_index)                                                 \
      WMLogWarning(_("key %s (0x%x) generates both %s and %s, which is nonsensical"), key_name, \
                   code, index_to_name(*old_mod), index_to_name(modifier_index));               \
    if (modifier_index == ShiftMapIndex) {                                                      \
      modbarf(key_name, "ModShift");                                                            \
    } else if (modifier_index == LockMapIndex) {                                                \
      modbarf(key_name, "ModLock");                                                             \
    } else if (modifier_index == ControlMapIndex) {                                             \
      modbarf(key_name, "ModControl");                                                          \
    } else if (modifier_index == super_bit && *old_mod != super_bit) {                          \
      modwarn(key_name, super_bit, "Super");                                                    \
    } else if (modifier_index == hyper_bit && *old_mod != hyper_bit) {                          \
      modwarn(key_name, hyper_bit, "Hyper");                                                    \
    } else if (modifier_index == alt_bit && *old_mod != alt_bit) {                              \
      modwarn(key_name, alt_bit, "Alt");                                                        \
    } else {                                                                                    \
      *(old_mod) = modifier_index;                                                              \
    }                                                                                           \
  }

        code = x_modifier_keymap->modifiermap[modifier_index * mkpm + modifier_key];
        sym = (code ? XkbKeycodeToKeysym(dpy, code, 0, column) : NoSymbol);

        if (sym == last_sym)
          continue;
        last_sym = sym;

        switch (sym) {
          case XK_Mode_switch:
            /* store_modifier("Mode_switch", &mode_bit); */
            mode_bit = modifier_index;
            break;
          case XK_Super_L:
            store_modifier("Super_L", &super_bit);
            break;
          case XK_Super_R:
            store_modifier("Super_R", &super_bit);
            break;
          case XK_Hyper_L:
            store_modifier("Hyper_L", &hyper_bit);
            break;
          case XK_Hyper_R:
            store_modifier("Hyper_R", &hyper_bit);
            break;
          case XK_Alt_L:
            store_modifier("Alt_L", &alt_bit);
            break;
          case XK_Alt_R:
            store_modifier("Alt_R", &alt_bit);
            break;
          case XK_Control_L:
            check_modifier("Control_L", ControlMask);
            break;
          case XK_Control_R:
            check_modifier("Control_R", ControlMask);
            break;
          case XK_Shift_L:
            check_modifier("Shift_L", ShiftMask);
            break;
          case XK_Shift_R:
            check_modifier("Shift_R", ShiftMask);
            break;
          case XK_Shift_Lock:
            check_modifier("Shift_Lock", LockMask);
            break;
          case XK_Caps_Lock:
            check_modifier("Caps_Lock", LockMask);
            break;

            /* It probably doesn't make any sense for a modifier bit to be
               assigned to a key that is not one of the above, but OpenWindows
               assigns modifier bits to a couple of random function keys for
               no reason that I can discern, so printing a warning here would
               be annoying. */
        }
      }
    }
  }

  /* mode_bit overrides everything, since it's processed down inside of
     XLookupString() instead of by us. */
  if (mode_bit) {
    const char *warn = NULL;

    if (mode_bit == hyper_bit) {
      warn = "Hyper", hyper_bit = 0;
    } else if (mode_bit == super_bit) {
      warn = "Super", super_bit = 0;
    } else if (mode_bit == alt_bit) {
      warn = "Alt", alt_bit = 0;
    }
    if (warn) {
      WMLogWarning("%s is being used for both Mode_switch and %s.", index_to_name(mode_bit), warn);
    }
  }

  HyperMask = (hyper_bit ? (1 << hyper_bit) : 0);
  SuperMask = (super_bit ? (1 << super_bit) : 0);
  AltMask = (alt_bit ? (1 << alt_bit) : 0);

  XFreeModifiermap(x_modifier_keymap);
#undef store_modifier
#undef modwarn
#undef check_modifier
#undef modbarf
}
